﻿namespace System.Modules.Contracts.DTO
{
    /// <summary>
    /// Updatable
    /// </summary>
    [Serializable]
    public abstract class Updatable
    {
        #region Properties
        /// <summary>
        /// Gets a value indicating whether the current Updatable object has a value.
        /// </summary>
        public abstract bool HasValue { get; }

        /// <summary>
        /// Gets whether this property has been updated.
        /// </summary>
        public abstract bool IsUpdated { get; set; }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the Underlying Type
        /// </summary>
        /// <returns></returns>
        public abstract Type GetUnderlyingType();

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <returns></returns>
        public abstract object GetValue();

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <returns></returns>
        public abstract void SetValue(object value);
        #endregion
    }

    /// <summary>
    /// Updatable of Type T
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public class Updatable<T> : Updatable
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public Updatable() { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="value"></param>
        public Updatable(T value)
        {
            Value = value;
        }

        #region Operators
        /// <summary>
        /// Implicit Operator
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator Updatable<T>(T value)
        {
            return new Updatable<T> { Value = value };
        }

        /// <summary>
        /// Implicit Operator
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator T(Updatable<T> value)
        {
            return value != null ? value.Value : default(T);
        }
        #endregion

        #region Fields
        private T _value;
        #endregion

        #region Properties
        /// <summary>
        /// Gets a value indicating whether the current Updatable object has a value.
        /// </summary>
        public override bool HasValue { get { return !Equals(default(T), Value); } }

        /// <summary>
        /// Gets whether this property has been updated.
        /// </summary>
        public override sealed bool IsUpdated { get; set; }

        /// <summary>
        /// Value
        /// </summary>
        public T Value
        {
            set
            {
                IsUpdated = (HasValue && !Equals(value, _value)) || (!HasValue && !Equals(value, default(T)));
                _value = value;
            }
            get
            {
                return _value;
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Overrides To String
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return !HasValue ? typeof(T).IsPrimitive ? default(T).ToString() : "NULL" : Value.ToString();
        }
        /// <summary>
        /// Gets the Underlying Type
        /// </summary>
        /// <returns></returns>
        public override Type GetUnderlyingType()
        {
            return typeof(T);
        }
        /// <summary>
        /// Get Value
        /// </summary>
        /// <returns></returns>
        public override object GetValue()
        {
            return Value;
        }
        /// <summary>
        /// Get Value
        /// </summary>
        /// <returns></returns>
        public override void SetValue(object value)
        {
            try
            {
                Value = (T)value;
            }
            catch
            {
                Value = default(T);
            }
        }
        #endregion
    }

    /// <summary>
    /// Updatable of Type T
    /// </summary>
    /// <typeparam name="TDefaultType"></typeparam>
    /// <typeparam name="TReturnType"> </typeparam>
    [Serializable]
    public class Updatable<TDefaultType, TReturnType> : Updatable<TDefaultType> where TReturnType : struct
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public Updatable() { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="value"></param>
        public Updatable(TDefaultType value) : base(value) { }

        #region Operators
        /// <summary>
        /// Implicit Operator
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator Updatable<TDefaultType, TReturnType>(TDefaultType value)
        {
            return new Updatable<TDefaultType, TReturnType>(value);
        }

        /// <summary>
        /// Implicit Operator
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator TDefaultType(Updatable<TDefaultType, TReturnType> value)
        {
            return value != null ? value.Value : default(TDefaultType);
        }

        /// <summary>
        /// Implicit Operator
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator TReturnType(Updatable<TDefaultType, TReturnType> value)
        {
            return value != null ? value.ReturnValue : default(TReturnType);
        }
        #endregion

        #region Properties
        /// <summary>
        /// ReturnValue
        /// </summary>
        public TReturnType ReturnValue
        {
            get
            {
                if (HasValue)
                {
                    var value = Convert.ChangeType(Value, typeof(TReturnType), null);
                    if (value != null)
                    {
                        return (TReturnType)value;
                    }
                    return default(TReturnType);
                }
                return default(TReturnType);
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Overrides To String
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return !HasValue ? typeof(TReturnType).IsPrimitive ? default(TReturnType).ToString() : "NULL" : Value.ToString();
        }
        /// <summary>
        /// Gets the Underlying Type
        /// </summary>
        /// <returns></returns>
        public override Type GetUnderlyingType()
        {
            return typeof(TDefaultType);
        }
        /// <summary>
        /// Get Value
        /// </summary>
        /// <returns></returns>
        public override object GetValue()
        {
            return Value;
        }
        /// <summary>
        /// Get Value
        /// </summary>
        /// <returns></returns>
        public override void SetValue(object value)
        {
            try
            {
                Value = (TDefaultType)value;
            }
            catch
            {
                Value = default(TDefaultType);
            }
        }
        #endregion
    }
}
